package com.cyj.dream.middleware.blackwhitelist.aspect;

import com.alibaba.fastjson.JSON;
import com.cyj.dream.middleware.blackwhitelist.annotation.DoWhiteList;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;

import javax.annotation.Resource;
import java.lang.reflect.Method;

/**
 * @Description: 切面逻辑实现
 * 1. @Retention(RetentionPolicy.RUNTIME)，Annotations are to be recorded in the class file by the compiler
 * and retained by the VM at run time, so they may be read reflectively.@Retention 是注解的注解，也称作元注解。
 * 这个注解里面有一个入参信息 RetentionPolicy.RUNTIME 在它的注释中有这样一段描述：Annotations are to be recorded in the class
 * file by the compiler and retained by the VM at run time, so they may be read reflectively. 其实说的就是加了这个注解，
 * 它的信息会被带到JVM运行时，当你在调用方法时可以通过反射拿到注解信息。除此之外，RetentionPolicy 还有两个属性 SOURCE、CLASS，
 * 其实这三个枚举正式对应了Java代码的加载和运行顺序，Java源码文件 -> .class文件 -> 内存字节码。并且后者范围大于前者，
 * 所以一般情况下只需要使用 RetentionPolicy.RUNTIME 即可。@Target 也是元注解起到标记作用，它的注解名称就是它的含义，目标，
 * 也就是我们这个自定义注解 DoWhiteList 要放在类、接口还是方法上。在 JDK1.8 中 ElementType 一共提供了10中目标枚举，
 * TYPE、FIELD、METHOD、PARAMETER、CONSTRUCTOR、LOCAL_VARIABLE、ANNOTATION_TYPE、PACKAGE、TYPE_PARAMETER、TYPE_USE，
 * 可以参考自己的自定义注解作用域进行设置自定义注解 @DoWhiteList 中有两个属性 key、returnJson。key 的作用是配置当前接口入参需要提取的属性，
 * returnJson 的作用是在我们拦截到用户请求后需要给一个返回信息。
 * @BelongsProject: DreamChardonnayCloud
 * @BelongsPackage: com.cyj.dream.whitelist
 * @Author: ChenYongJia
 * @CreateTime: 2021-09-14 21:28
 * @Email: chen87647213@163.com
 * @Version: 1.0
 */
@Slf4j
@Aspect
//@Component
public class DoJoinPoint {

    @Resource
    @Value("${sunny.whitelist}")
    private String whiteListConfig;

    /**
     * @return void
     * 1. @Pointcut("@annotation(com.cyj.dream.middleware.blackwhitelist.annotation.DoWhiteList)")，定义切点。
     * 在 Pointcut 中提供了很多的切点寻找方式，有指定方法名称的、有范围筛选表达式的，也有我们现在通过自定义注解方式的。
     * 一般在中间件开发中，自定义注解方式使用的比较多，因为它可以更加灵活的运用到各个业务系统中。
     *
     * @date 2021/6/24 11:53
     * @author ChenYongJia
     * @version 1.0
     */
    @Pointcut("@annotation(com.cyj.dream.middleware.blackwhitelist.annotation.DoWhiteList)")
    public void aopPoint() {
    }

    /**
     * 配置置入点--对方法增强的织入动作
     * 效果就是在你调用已经加了自定义注解 @DoWhiteList 的方法时，会先进入到此切点增强的方法。
     * 那么这个时候就你可以做一些对方法的操作动作了，比如我们实现的白名单用户拦截还是放行。
     * <p>
     * 对当前拦截方法校验结果的操作，是拦截还是放行。其实拦截就是返回我们在自定义注解配置的 JSON 信息生成对象返回，
     * 放行则是调用 jp.proceed(); 方法，让整个代码块向下继续执行。
     *
     * @param jp 织入点信息
     * @return java.lang.Object
     * @date 2021/6/24 11:53
     * @author ChenYongJia
     * @version 1.0
     */
    @Around("aopPoint()")
    public Object doRouter(ProceedingJoinPoint jp) throws Throwable {
        // 获取内容
        Method method = getMethod(jp);
        DoWhiteList whiteList = method.getAnnotation(DoWhiteList.class);

        // 获取字段值
        String keyValue = getFiledValue(whiteList.key(), jp.getArgs());
        log.info("whitelist whitelist handler method：{} value：{}", method.getName(), keyValue);
        if (null == keyValue || "".equals(keyValue)) return jp.proceed();

        String[] split = whiteListConfig.split(",");

        // 白名单过滤
        for (String str : split) {
            if (keyValue.equals(str)) {
                // 调用 jp.proceed(); 方法让整个代码块向下继续执行。
                return jp.proceed();
            }
        }

        // 拦截
        return returnObject(whiteList, method);
    }

    /**
     * 通过反射获取方法信息
     *
     * @param * @param jp
     * @return * @return java.lang.reflect.Method
     * @author ChenYongJia
     * @date 13:32 2021/9/15
     */
    private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
        Signature sig = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) sig;
        return jp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
    }

    /**
     * 返回对象--拦截使用
     *
     * @param whiteList 白名单注解
     * @param method    方法信息
     * @return * @return java.lang.Object
     * @author ChenYongJia
     * @date 13:31 2021/9/15
     */
    private Object returnObject(DoWhiteList whiteList, Method method) throws IllegalAccessException, InstantiationException {
        Class<?> returnType = method.getReturnType();
        String returnJson = whiteList.returnJson();
        if ("".equals(returnJson)) {
            return returnType.newInstance();
        }
        return JSON.parseObject(returnJson, returnType);
    }

    /**
     * 获取属性值
     *
     * @param filed 文件信息
     * @param args  args对象数组
     * @return * @return java.lang.String
     * @author ChenYongJia
     * @date 13:31 2021/9/15
     */
    private String getFiledValue(String filed, Object[] args) {
        String filedValue = null;
        for (Object arg : args) {
            try {
                if (null == filedValue || "".equals(filedValue)) {
                    filedValue = BeanUtils.getProperty(arg, filed);
                } else {
                    break;
                }
            } catch (Exception e) {
                if (args.length == 1) {
                    return args[0].toString();
                }
            }
        }
        return filedValue;
    }

}
